

#include "src/emule/aich_hash_tree.h"

#include "src/utility/log.h"
#include "src/emule/emule_define.h"
#include "src/io/file.h"
#include "src/emule/aich_hash_sha1.h"

jellyfish::emule::aich_hash_tree::aich_hash_tree()
: size_(0)
, block_size_(0)
, is_left_branch_(false)
, left_child_(NULL)
, right_child_(NULL)
, is_hash_valid_(false)
{
}

jellyfish::emule::aich_hash_tree::aich_hash_tree( uint64 size,uint64 block_size,bool is_left_branch )
: size_(size)
, block_size_(block_size)
, is_left_branch_(is_left_branch)
, left_child_(NULL)
, right_child_(NULL)
, is_hash_valid_(false)
{
}

void jellyfish::emule::aich_hash_tree::set_size( uint64 size )
{
	size_ = size;
	block_size_ = size_<=PARTSIZE ? EMBLOCKSIZE:PARTSIZE; 
}

jellyfish::emule::aich_hash_tree* jellyfish::emule::aich_hash_tree::find_hash( uint64 start,uint64 length )
{
	uint8 level = 0;
	return find_hash(start,length,&level);
}

jellyfish::emule::aich_hash_tree* jellyfish::emule::aich_hash_tree::find_hash( uint64 start,uint64 length,uint8* level )
{
	++(*level);
	assert(*level <= 22);
	assert(start + length <= size_);
	assert(start + length <= size_);
	assert(length <= size_);

	if (0 == start && length == size_)
	{
		return this;
	}
	else if (size_ <= block_size_)
	{
		return NULL;
	}
	else
	{
		uint64 blocks = size_ / block_size_ + size_ % block_size_ != 0 ? 1: 0;
		uint64 left_size = (is_left_branch_? blocks + 1 : blocks) / 2 * block_size_;
		uint64 right_size = size_ - left_size;
		LOG_DEBUG("blocks = "<<blocks);
		LOG_DEBUG("size_ = "<<size_);
		LOG_DEBUG("left_size = "<<left_size);
		LOG_DEBUG("right_size = "<<right_size);
		LOG_DEBUG("");

		if (start < left_size)
		{
			if (start + length > left_size)
			{
				assert(false);
				return NULL;
			}
			if (NULL == left_child_)
			{
				left_child_ = new aich_hash_tree(left_size,left_size > PARTSIZE? PARTSIZE : EMBLOCKSIZE,true);
				return left_child_->find_hash(start,length,level);
			}
		} 
		else
		{
			start -= left_size;
			if (start + length > right_size)
			{
				assert(false);
				return NULL;
			}
			if (NULL == right_child_)
			{
				right_child_ = new aich_hash_tree(right_size,right_size > PARTSIZE? PARTSIZE : EMBLOCKSIZE,false);
				return right_child_->find_hash(start,length,level);
			}
		}
	}
	return NULL;
}

uint64 jellyfish::emule::aich_hash_tree::create_hash( const uint8* in_buf,uint64 in_buf_length,uint8* out_hash, 
													 uint64 out_hash_length,aich_hash_tree* tree )
{
	assert(NULL!=in_buf && in_buf_length>0);
	assert(NULL!=out_hash && out_hash_length>0);
	assert(NULL!=tree);

	// assume in_buf_length = PARTSIZE
	uint64 length = in_buf_length;
	uint8 data[64*128] = {0};
	uint64 pos = 0;
	uint64 block_hash_pos = 0;

	aich_hash_algorithm* hash_algorithm = new aich_hash_sha1;

	while (length >= 64)
	{
		uint32 len;
		if ((length / 64) > sizeof(data)/(64 * sizeof(data[0])))
			len = sizeof(data)/(64 * sizeof(data[0]));
		else
			len = (uint32)length / 64;
		LOG_DEBUG("[create_hash] len = "<<len);
		LOG_DEBUG("[create_hash] length = "<<length);
		LOG_DEBUG("[create_hash] pos = "<<pos);
		
		//memcpy(data,in_buf,len*64);
		if (pos+len*64>=EMBLOCKSIZE)
		{
			uint32 to_complete = (uint32)(EMBLOCKSIZE - pos);
			
			hash_algorithm->add(data, to_complete);
			assert( pos + to_complete == EMBLOCKSIZE );
			//tree->SetBlockHash(EMBLOCKSIZE, block_hash_pos, pHashAlg);
			block_hash_pos += EMBLOCKSIZE;
			hash_algorithm->reset();
			hash_algorithm->add(data+to_complete, (len*64)-to_complete);
			pos = (len*64) - to_complete;
			LOG_DEBUG("[create_hash] to_complete = "<<to_complete);
			LOG_DEBUG("[create_hash] block_hash_pos = "<<block_hash_pos);
		}
		else
		{
			pos += len * 64;
			hash_algorithm->add(data, len * 64);
		}

		length -= len*64;
	}

	return 1;
}

void jellyfish::emule::aich_hash_tree::SetBlockHash( uint64 nSize, uint64 nStartPos, aich_hash_algorithm* pHashAlg )
{
	assert ( nSize <= EMBLOCKSIZE );
	aich_hash_tree* pToInsert = find_hash(nStartPos, nSize);
	if (pToInsert == NULL){
		assert ( false );
		return;
	}

	if (pToInsert->get_block_size() != EMBLOCKSIZE || pToInsert->get_size() != nSize){
		assert ( false );
		return;
	}

	pHashAlg->finish(pToInsert->get_aich_hash());
	pToInsert->is_hash_valid_ = true;
}

uint64 jellyfish::emule::aich_hash_tree::get_size() const
{
	return size_;
}

uint64 jellyfish::emule::aich_hash_tree::get_block_size() const
{
	return block_size_;
}

jellyfish::emule::aich_hash& jellyfish::emule::aich_hash_tree::get_aich_hash()
{
	return aich_hash_;
}
